/*
 *    Copyright 2022 The DSMS Authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.dsms.modules.filesystem.service.impl;

import com.alibaba.fastjson2.JSON;
import com.dsms.common.constant.ResultCode;
import com.dsms.common.constant.StepTypeEnum;
import com.dsms.common.constant.TaskStatusEnum;
import com.dsms.common.constant.TaskTypeEnum;
import com.dsms.common.exception.DsmsEngineException;
import com.dsms.common.remotecall.model.RemoteResponse;
import com.dsms.common.taskmanager.TaskContext;
import com.dsms.common.taskmanager.TaskException;
import com.dsms.common.taskmanager.model.AsyncStep;
import com.dsms.common.taskmanager.model.AsyncTask;
import com.dsms.common.taskmanager.model.Step;
import com.dsms.common.taskmanager.model.Task;
import com.dsms.common.taskmanager.service.IStepService;
import com.dsms.common.taskmanager.service.ITaskService;
import com.dsms.common.validator.DsmsValidateGroup;
import com.dsms.dfsbroker.common.api.CommonApi;
import com.dsms.dfsbroker.filesystem.FileSystem;
import com.dsms.dfsbroker.filesystem.api.FileSystemApi;
import com.dsms.dfsbroker.filesystem.model.dto.FileSystemDTO;
import com.dsms.dfsbroker.filesystem.model.remote.FsStatusResponse;
import com.dsms.dfsbroker.filesystem.service.IFileSystemService;
import com.dsms.dfsbroker.storagepool.model.StoragePool;
import com.dsms.dfsbroker.storagepool.service.IStoragePoolService;
import com.dsms.modules.util.RemoteCallUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;

import javax.validation.Valid;
import java.util.Collections;
import java.util.List;

@Service
@Slf4j
@Validated
public class FileSystemServiceImpl implements IFileSystemService {

    private final FileSystemApi fileSystemApi;

    private final IStoragePoolService storagePoolService;

    private final TaskContext taskContext;

    private final ITaskService taskService;

    private final IStepService stepService;

    public FileSystemServiceImpl(FileSystemApi fileSystemApi, IStoragePoolService storagePoolService,
        TaskContext taskContext, ITaskService taskService, IStepService stepService) {
        this.fileSystemApi = fileSystemApi;
        this.storagePoolService = storagePoolService;
        this.taskContext = taskContext;
        this.taskService = taskService;
        this.stepService = stepService;
    }

    @Override
    public List<FileSystem> list() {
        FsStatusResponse fsStatusResponse = null;
        try {
            fsStatusResponse = fileSystemApi.getFsStatus(RemoteCallUtil.generateRemoteRequest());
        } catch (Throwable e) {
            log.error("get file system from dsms-storage fail,fail message:{}", e.getMessage(), e);
            throw DsmsEngineException.exceptionWithThrowable(e, ResultCode.FS_LIST_ERROR);
        }
        FileSystem fileSystem = FileSystem.fsStatusResponseParseFileSystem(fsStatusResponse);
        if (fileSystem == null) {
            return Collections.emptyList();
        }
        StoragePool dataPool = storagePoolService.get(fileSystem.getDataPoolName());
        fileSystem.setDataPoolTotalSize(dataPool.getTotalSize());
        fileSystem.setDataPoolUsedSize(dataPool.getUsedSize());
        return Collections.singletonList(fileSystem);
    }

    @Override
    public boolean createFs(FileSystemDTO fileSystemDTO) {
        //1. Verify that the create file system task param
        String taskMessage = validateCreateFileSystem(fileSystemDTO);

        //2. create add file system task
        RemoteResponse response = null;
        try {
            response = fileSystemApi.createFs(RemoteCallUtil.generateRemoteRequest(), fileSystemDTO);
            if (ObjectUtils.isEmpty(response.getId())) {
                throw new TaskException("get dsms-storage create fs request id fail");
            }
            fileSystemDTO.setRequestId(response.getId());
        } catch (Throwable e) {
            log.error("create file system task fail,fail message:{},response:{}", e.getMessage(), JSON.toJSONString(response), e);
            throw DsmsEngineException.exceptionWithThrowable(e, ResultCode.FS_CREATE_TASK_ERROR);
        }

        Task createFs = new AsyncTask(
                TaskTypeEnum.CREATE_FS.getName(),
                TaskTypeEnum.CREATE_FS.getType(),
                TaskStatusEnum.QUEUE.getStatus(),
                taskMessage,
                JSON.toJSONString(fileSystemDTO));

        return taskService.save(createFs);
    }

    private String validateCreateFileSystem(FileSystemDTO fileSystemDTO) {
        String taskMessage = TaskTypeEnum.CREATE_FS.getName() + ":" + fileSystemDTO.getFsName() + ", 元数据池:" + fileSystemDTO.getMetadata() + ", 数据池:" + fileSystemDTO.getData();

        if (!storagePoolService.isPoolAvailable(fileSystemDTO.getMetadata())) {
            throw DsmsEngineException.exceptionWithMessage(fileSystemDTO.getMetadata(), ResultCode.POOL_UNAVAILABLE_ERROR);
        }
        if (!storagePoolService.isPoolAvailable(fileSystemDTO.getData())) {
            throw DsmsEngineException.exceptionWithMessage(fileSystemDTO.getData(), ResultCode.POOL_UNAVAILABLE_ERROR);
        }
        if (!taskContext.validateTask(TaskTypeEnum.CREATE_FS, taskMessage)) {
            throw new DsmsEngineException(ResultCode.FS_CREATE_TASK_EXIST);
        }

        return taskMessage;
    }

    @Override
    @Validated(DsmsValidateGroup.Remove.class)
    public boolean deleteFs(@Valid FileSystemDTO fileSystemDTO) {
        //1. Verify that the remove file system task exists, rely on task message filed and task status
        String taskMessage = TaskTypeEnum.REMOVE_FS.getName() + ":" + fileSystemDTO.getFsName();
        if (!taskContext.validateTask(TaskTypeEnum.REMOVE_FS, taskMessage)) {
            log.warn("Task already exist,task message:{}", taskMessage);
            throw new DsmsEngineException(ResultCode.FS_REMOVE_TASK_EXIST);
        }
        try {
            fileSystemApi.getFsLs(RemoteCallUtil.generateRemoteRequest())
                    .forEach(fs -> {
                        fileSystemDTO.setMetadata(fs.getMetadataPool());
                        fs.getDataPools().forEach(fileSystemDTO::setData);
                    });
        } catch (Throwable e) {
            log.error("get fs used pool list fail,fail message:{}", e.getMessage(), e);
            throw DsmsEngineException.exceptionWithThrowable(e, ResultCode.FS_REMOVE_TASK_ERROR);
        }

        try {
            Task removeFsTask = new AsyncTask(
                    TaskTypeEnum.REMOVE_FS.getName(),
                    TaskTypeEnum.REMOVE_FS.getType(),
                    TaskStatusEnum.QUEUE.getStatus(),
                    taskMessage,
                    JSON.toJSONString(fileSystemDTO));

            if (taskService.save(removeFsTask)) {
                deleteStepTask(removeFsTask.getId(), fileSystemDTO);
            }
        } catch (Throwable e) {
            log.error("delete file system task fail,fail message:{},task info:{}", e.getMessage(), JSON.toJSONString(taskMessage), e);
            throw DsmsEngineException.exceptionWithThrowable(e, ResultCode.FS_REMOVE_TASK_ERROR);
        }

        return true;
    }

    private boolean deleteStepTask(int taskId, FileSystemDTO fileSystemDTO) {

        //0.delete file system auth id
        Step deleteFileSystemAuth = new AsyncStep(
                taskId,
                StepTypeEnum.DELETE_FILE_SYSTEM_AUTH.getName(),
                StepTypeEnum.DELETE_FILE_SYSTEM_AUTH.getType(),
                StepTypeEnum.DELETE_FILE_SYSTEM_AUTH.getName() + ":" + fileSystemDTO.getFsName(),
                TaskStatusEnum.QUEUE.getStatus(),
                JSON.toJSONString(fileSystemDTO));

        if (!stepService.save(deleteFileSystemAuth)) {
            throw new TaskException("the task for delete file system auth id step failed");
        }

        //1. bring the file system down step
        Step failFsStep = new AsyncStep(
                taskId,
                StepTypeEnum.FAIL_FILE_SYSTEM.getName(),
                StepTypeEnum.FAIL_FILE_SYSTEM.getType(),
                StepTypeEnum.FAIL_FILE_SYSTEM.getName() + ":" + fileSystemDTO.getFsName(),
                TaskStatusEnum.QUEUE.getStatus(),
                JSON.toJSONString(fileSystemDTO));

        if (!stepService.save(failFsStep)) {
            throw new TaskException("the task for bring the file system down step failed");
        }

        //2.delete file system step
        Step deleteFileSystemStep = new AsyncStep(
                taskId,
                StepTypeEnum.DELETE_FILE_SYSTEM.getName(),
                StepTypeEnum.DELETE_FILE_SYSTEM.getType(),
                StepTypeEnum.DELETE_FILE_SYSTEM.getName() + ":" + fileSystemDTO.getFsName(),
                TaskStatusEnum.QUEUE.getStatus(),
                JSON.toJSONString(fileSystemDTO));

        if (!stepService.save(deleteFileSystemStep)) {
            throw new TaskException("the task for delete file system step failed");
        }

        //3.purge file system used pool step
        Step purgeFileSystemPool = new AsyncStep(
                taskId,
                StepTypeEnum.PURGE_FILE_SYSTEM_POOL.getName(),
                StepTypeEnum.PURGE_FILE_SYSTEM_POOL.getType(),
                StepTypeEnum.PURGE_FILE_SYSTEM_POOL.getName() + ":" + fileSystemDTO.getFsName(),
                TaskStatusEnum.QUEUE.getStatus(),
                JSON.toJSONString(fileSystemDTO));

        if (!stepService.save(purgeFileSystemPool)) {
            throw new TaskException("the task for purge file system used pool step failed");
        }

        return true;
    }
}
